package com.lmy.springCloud.contorller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.lmy.springCloud.entity.Member;
import com.lmy.springCloud.handler.CustomGlobalBlockHandler;
import com.lmy.springCloud.handler.CustomGlobalFallbackHandler;
import com.lmy.springCloud.service.MemberService;
import com.lmy.springCloud.utils.Result;
import com.lmy.springCloud.utils.ResultEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.sql.Time;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
public class MemberController {

    @Value(value = "${spring.application.name}")
    private String serverName;

    @Value(value = "${server.port}")
    private String serverPort;

    @Resource
    private MemberService memberService;

    //执行的计数器
    private static int num = 0;

    @RequestMapping(value = "/getMemberById/{id}", method = RequestMethod.GET)
    public Result<Member> getMemberById(@PathVariable(value = "id") Long id) {
        //模拟sentinel通过线程数设置流控规则
//        try{
//            //模拟超时，让线程休眠1s
//            TimeUnit.MILLISECONDS.sleep(2000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//        log.info("enter getMemberById.... 当前线程id={}, 时间={}", Thread.currentThread().getId(), new Date());
        Member member = memberService.queryMemberById(id);
        return member != null ? Result.success(member, serverName + "-" + serverPort) : Result.info(ResultEnum.QUERY_NONE);
    }
    //通过?携带参数，便于sentinel对该接口实行通配符的流控规则，即携带参数进行限流
//    @RequestMapping(value = "/getMemberById", params = "id", method = RequestMethod.GET)
//    public Result<Member> getMemberById(Long id) {
//        Member member = memberService.queryMemberById(id);
//        return member != null ? Result.success(member, serverName + "-" + serverPort) : Result.info(ResultEnum.QUERY_NONE);
//    }

    @RequestMapping(value = "/saveMember", method = RequestMethod.POST)
    public Result saveMember(@RequestBody Member member) {
        log.info("saveMember: " + member);
        int affected = memberService.saveMember(member);
        return affected > 0 ? Result.info(ResultEnum.SAVE_OK) : Result.info(ResultEnum.SAVE_ERROR);
    }

    //模拟sentinel通过关联关系设置流控规则
    //此时t2是关联接口，t1是被限流的接口，也是需要设置流控规则的接口
    //即t2达到一定访问量时，限制t1的访问
    @GetMapping(value = "/t1")
    public Result t1(){
        return Result.success(null, "t1()执行..");
    }

    @GetMapping(value = "/t2")
    public Result t2(){
        //模拟sentinel通过排队设置流控规则，设置QPS为1，超时时间为10s
        //让线程休眠1s，模拟执行时间为1s => 当多少个请求，就会造成超时?第10个
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //输出线程信息
        log.info("执行t2()线程id={}", Thread.currentThread().getId());
        return Result.success(null, "t2()执行..");
    }

    //模拟sentinel实现慢调用比例熔断
    @GetMapping(value = "/t3")
    public Result t3(){
        //让线程休眠300ms，模拟执行时间
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return Result.success(null, "t3()执行...");
    }

    //模拟sentinel实现异常比例熔断
    @GetMapping(value = "/t4")
    public Result t4() {
        //设计异常比例达到50%
        if (++num % 2 == 0) {
            //制造一个异常
            int res = 3 / 0;
        }
        log.info("熔断降级测试[异常比例] 执行t4 线程id={}", Thread.currentThread().getId());
        return Result.success(null, "t4()执行...");
    }

    //模拟sentinel实现异常数熔断(异常数统计是分钟级别的，即资源在1分钟的异常数目超过阈值后会进入熔断)
    @GetMapping(value = "/t5")
    public Result t5() {
        //设计测试案例，满足异常数阈值
        //出现了10次异常，这里需要设置大于6，需要留出几次做测试和加入簇点链路
        if (++num <= 10) {
            int res = 3 / 0;
        }
        log.info("熔断降级测试[异常数] 执行t5 线程id={}", Thread.currentThread().getId());
        return Result.success(null, "t5()执行...");
    }

    //模拟sentinel热点限流    /news?id=x&type=x
    /*
        当配置的资源名news触发限流机制时，会调用newBlockHandler方法
        1.@SentinelResource 指定sentinel限流资源
        2.value = "news" 表示限流资源的名称
        3.blockHandler = "newsBlockHandler" 当出现限流时，由指定方法进行处理
     */
    @GetMapping("/news")
    @SentinelResource(value = "news", blockHandler = "newsBlockHandler")
    public Result QueryNews(@RequestParam(value = "id", required = false) String id,
                            @RequestParam(value = "type", required = false) String type) {
        //模拟数据库/缓存数据
        log.info("到DB 查询新闻");
        return Result.success(null, "返回id= " + id + "的新闻");
    }

    //热点key限流异常处理方法
    public Result newsBlockHandler(String id, String type, BlockException blockException) {
        return Result.error(null, "查询id=" + id + "的新闻触发了热点key限流保护...");
    }


    /*
        1.value = "t6" 表示限流资源的名称
        2.blockHandlerClass = CustomGlobalBlockHandler.class 当该资源触发限流机制时，使用指定全局限流处理类进行处理
        3.blockHandler = "handlerMethod1" 当出现限流时，由全局限流处理类的指定方法进行处理
        4.fallbackClass = CustomGlobalFallbackHandler.class 当该资源触发java异常时，使用指定全局异常处理类进行处理
        5.fallback = "fallbackHandlerMethod1" 当出现异常时，由全局异常处理类的指定方法进行处理
        6.exceptionsToIgnore = {RuntimeException.class} 可以排除异常，表示如果t6()抛出了RuntimeException，就不再处理异常
        7.如果是以@SentinelResource 注解方式接入sentinel，方法不能用private修饰
     */
    //使用全局限流处理类，显示限流信息
    @GetMapping(value = "/t6")
    @SentinelResource(value = "t6", blockHandlerClass = CustomGlobalBlockHandler.class,
            blockHandler = "handlerMethod1",
            fallbackClass = CustomGlobalFallbackHandler.class,
            fallback = "fallbackHandlerMethod1",
            exceptionsToIgnore = {RuntimeException.class})
    public Result t6() {
        //假定当访问t6资源的次数是5的倍数时，就出现一个java异常
        if (++num % 5 == 0) {
            throw new RuntimeException("num值异常 num=" + num);
        }
        log.info("执行t6() 线程id={}", Thread.currentThread().getId());
        return Result.success(null, "t6()执行..");
    }

}
